feat(agent): worker cloud-init + systemd + bearer auth (FJB-94 Phase B)#111
Merged
Conversation
Lands the deployment story for fjbagent on workers — cloud-init
installs the binary, drops a systemd unit + env file + auth token,
creates the unprivileged `fjb` system user, enables the service.
Opt-in: empty -fjbagent-token-file disables agent install entirely
so this is a no-op for deployments that haven't migrated yet.
Per-deployment knobs:
- -fjbagent-token-file <path> shared-secret bearer-token file;
empty = agent install disabled.
- -fjbagent-url <template> URL template; {{.Version}} resolves
to this daemon's build version,
$rarch is left for cloud-init to
substitute at boot. Default points
at github releases for the matching
fj-bellows version.
The agent's version is implicitly the same as the orchestrator's
build (linker-stamped main.version) — there is no per-deployment
agent version flag, because agent + orchestrator share a proto and
must come from the same commit. Pre-existing workers from before an
orchestrator upgrade keep running the agent version they were
provisioned with; the proto's wire-compatibility discipline (no
field renumbering, additive only) is the contract that keeps the
upgraded orchestrator able to dial them.
Deploy artifacts (added to cloud-init.yaml.tmpl, gated on
FJBAgentDownloadURL being non-empty):
- groups: [fjb], users: [{name: fjb, system, nologin}]
- /etc/fjbagent/auth.token (mode 0600, owned by fjb:fjb)
- /etc/default/fjbagent (FJBAGENT_LISTEN=0.0.0.0:9001)
- /etc/systemd/system/fjbagent.service (Type=notify, hardening
flags from the design doc)
- /usr/local/bin/fjbagent fetched from the resolved URL
- systemctl enable --now fjbagent
Agent gets sd_notify support so Type=notify works — net.DialUnix to
$NOTIFY_SOCKET, write "READY=1\n", inline (no go-systemd dep).
NOTE: this PR ships the wiring but does not by itself publish the
fjbagent binary at the default github releases URL. The release
pipeline (.github/workflows/ci.yml) needs a parallel change to
cross-compile fjbagent for linux-amd64 / linux-arm64 and attach the
artifacts to v* tag releases before any deployment can set
-fjbagent-token-file in production. Tracked in FJB-94 phase
follow-ups.
Tests:
- 5 new bootstrap render tests: backward-compat (no artifacts when
URL empty), full-render asserts (user creation, token file mode,
systemd unit text, runcmd), token-required guard, URL resolution
across default + custom templates.
- Existing render tests + full repo `go test -race ./...` green.
- golangci-lint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Phase B token-load added one branch to run() that pushed it from 13 to 14, which is over .golangci.yml's gocyclo budget. Extract the orchestrator.Config construction (and the FJB-94 token-load that sits in front of it) into buildOrchestratorConfig so run()'s complexity is back under. Locally golangci-lint v2.12.1 didn't flag this in my workspace — CI's run on Linux did. Cause: probably gocyclo cache differences. The fix is purely structural; no behaviour change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…B-94 Phase B) CI's gocyclo flagged run() at complexity 14 even after the earlier buildOrchestratorConfig extraction — main has moved forward since Phase B branched, and the new plumbOrchestratorWGPubkey branch pushed it back over. Extract two more small helpers to bring it down: loadSSHKeyForProvider folds the docker-vs-ssh check, and applyAuthorizedKeyToLinodeProvider folds the Linode duck-type wiring. Behaviour unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second slice of FJB-94. Lands the worker deployment story for
fjbagent. Cache deploy + orchestrator dialer is Phase C. Opt-in: empty-fjbagent-token-filekeeps current behaviour unchanged.Design decisions worth flagging
main.version). No-fjbagent-versionflag.service AgentServicedoc comment.Type=notifyfor the systemd unit. Inlined theNOTIFY_SOCKETprotocol ininternal/agent/sdnotify.go(4 syscalls, nogo-systemddep).Wiring
Cloud-init artifacts (gated on
Params.FJBAgentDownloadURLbeing non-empty):fjbsystem user + group (nologin, no home)/etc/fjbagent/auth.tokenmode 0600 owned byfjb:fjb/etc/default/fjbagentwithFJBAGENT_LISTEN=0.0.0.0:9001/etc/systemd/system/fjbagent.service(NoNewPrivileges, ProtectSystem=strict, hardening from the design doc)/usr/local/bin/fjbagentfetched from the resolved URLsystemctl enable --now fjbagentHeads-up — release pipeline TODO
The default URL points at
github.com/hstern/fj-bellows/releases/download/v<orchestrator-version>/fjbagent-linux-$rarch. The current release pipeline does not yet publish that artifact — it only publishes the docker image. A follow-up.github/workflows/ci.ymlchange is needed to cross-compile fjbagent on tag pushes before any deployment can set-fjbagent-token-fileand have it actually work. Until that lands, the flag is dormant (legacy default behaviour preserved).Test plan
go test -race ./...full repo green.golangci-lintclean.🤖 Generated with Claude Code